@@ -1,5 +1,6 @@ |
||
1 | 1 |
# Changes |
2 | 2 |
|
3 |
+* 0.3 (Dec 22, 2013) - Agents now have an optional keep_events_for option that is propagated to created events' expires_at field for eventual cleanup. |
|
3 | 4 |
* 0.2 (Nov 6, 2013) - PeakDetectorAgent now uses `window_duration_in_days` and `min_peak_spacing_in_days`. Additionally, peaks trigger when the time series rises over the standard deviation multiple, not after it starts to fall. |
4 | 5 |
* June 29, 2013 - Removed rails\_admin because it was causing deployment issues. Better to have people install their favorite admin tool if they want one. |
5 | 6 |
* June, 2013 - A number of new agents have been contributed, including interfaces to Weibo, Twitter, and Twilio, as well as Agents for translation, sentiment analysis, and for posting and receiving webhooks. |
@@ -34,6 +34,12 @@ showLinks = -> |
||
34 | 34 |
$(".link-region .cannot-receive-events").hide() |
35 | 35 |
showEventDescriptions() |
36 | 36 |
|
37 |
+hideEventCreation = -> |
|
38 |
+ $(".event-related-region").hide() |
|
39 |
+ |
|
40 |
+showEventCreation = -> |
|
41 |
+ $(".event-related-region").show() |
|
42 |
+ |
|
37 | 43 |
showEventDescriptions = -> |
38 | 44 |
if $("#agent_source_ids").val() |
39 | 45 |
$.getJSON "/agents/event_descriptions", { ids: $("#agent_source_ids").val().join(",") }, (json) => |
@@ -131,6 +137,11 @@ $(document).ready -> |
||
131 | 137 |
else |
132 | 138 |
hideLinks() |
133 | 139 |
|
140 |
+ if json.can_create_events |
|
141 |
+ showEventCreation() |
|
142 |
+ else |
|
143 |
+ hideEventCreation() |
|
144 |
+ |
|
134 | 145 |
$(".description").html(json.description_html) if json.description_html? |
135 | 146 |
|
136 | 147 |
if $("#agent_options").hasClass("showing-default") || $("#agent_options").val().match(/\A\s*(\{\s*\}|)\s*\Z/g) |
@@ -152,3 +163,9 @@ $(document).ready -> |
||
152 | 163 |
showLinks() |
153 | 164 |
else |
154 | 165 |
hideLinks() |
166 |
+ |
|
167 |
+ if $(".event-related-region") |
|
168 |
+ if $(".event-related-region").data("can-create-events") == true |
|
169 |
+ showEventCreation() |
|
170 |
+ else |
|
171 |
+ hideEventCreation() |
@@ -33,6 +33,7 @@ class AgentsController < ApplicationController |
||
33 | 33 |
render :json => { |
34 | 34 |
:can_be_scheduled => agent.can_be_scheduled?, |
35 | 35 |
:can_receive_events => agent.can_receive_events?, |
36 |
+ :can_create_events => agent.can_create_events?, |
|
36 | 37 |
:options => agent.default_options, |
37 | 38 |
:description_html => agent.html_description |
38 | 39 |
} |
@@ -16,7 +16,9 @@ class Agent < ActiveRecord::Base |
||
16 | 16 |
SCHEDULES = %w[every_2m every_5m every_10m every_30m every_1h every_2h every_5h every_12h every_1d every_2d every_7d |
17 | 17 |
midnight 1am 2am 3am 4am 5am 6am 7am 8am 9am 10am 11am noon 1pm 2pm 3pm 4pm 5pm 6pm 7pm 8pm 9pm 10pm 11pm] |
18 | 18 |
|
19 |
- attr_accessible :options, :memory, :name, :type, :schedule, :source_ids |
|
19 |
+ EVENT_RETENTION_SCHEDULES = [["Forever", 0], ["1 day", 1], *([2, 3, 4, 5, 7, 14, 21, 30, 45, 90, 180, 365].map {|n| ["#{n} days", n] })] |
|
20 |
+ |
|
21 |
+ attr_accessible :options, :memory, :name, :type, :schedule, :source_ids, :keep_events_for |
|
20 | 22 |
|
21 | 23 |
validates_presence_of :name, :user |
22 | 24 |
validate :sources_are_owned |
@@ -83,22 +85,16 @@ class Agent < ActiveRecord::Base |
||
83 | 85 |
most_recent_log.try(:level) == 4 |
84 | 86 |
end |
85 | 87 |
|
86 |
- def sources_are_owned |
|
87 |
- errors.add(:sources, "must be owned by you") unless sources.all? {|s| s.user == user } |
|
88 |
- end |
|
89 |
- |
|
90 | 88 |
def create_event(attrs) |
91 | 89 |
if can_create_events? |
92 |
- events.create!({ :user => user }.merge(attrs)) |
|
90 |
+ events.create!({ :user => user, :expires_at => new_event_expiration_date }.merge(attrs)) |
|
93 | 91 |
else |
94 | 92 |
error "This Agent cannot create events!" |
95 | 93 |
end |
96 | 94 |
end |
97 | 95 |
|
98 |
- def validate_schedule |
|
99 |
- unless cannot_be_scheduled? |
|
100 |
- errors.add(:schedule, "is not a valid schedule") unless SCHEDULES.include?(schedule.to_s) |
|
101 |
- end |
|
96 |
+ def new_event_expiration_date |
|
97 |
+ keep_events_for > 0 ? keep_events_for.days.from_now : nil |
|
102 | 98 |
end |
103 | 99 |
|
104 | 100 |
def make_message(payload, message = options[:message]) |
@@ -112,14 +108,6 @@ class Agent < ActiveRecord::Base |
||
112 | 108 |
end |
113 | 109 |
end |
114 | 110 |
|
115 |
- def set_default_schedule |
|
116 |
- self.schedule = default_schedule unless schedule.present? || cannot_be_scheduled? |
|
117 |
- end |
|
118 |
- |
|
119 |
- def unschedule_if_cannot_schedule |
|
120 |
- self.schedule = nil if cannot_be_scheduled? |
|
121 |
- end |
|
122 |
- |
|
123 | 111 |
def last_event_at |
124 | 112 |
@memoized_last_event_at ||= most_recent_event.try(:created_at) |
125 | 113 |
end |
@@ -152,12 +140,6 @@ class Agent < ActiveRecord::Base |
||
152 | 140 |
!cannot_create_events? |
153 | 141 |
end |
154 | 142 |
|
155 |
- def set_last_checked_event_id |
|
156 |
- if newest_event_id = Event.order("id desc").limit(1).pluck(:id).first |
|
157 |
- self.last_checked_event_id = newest_event_id |
|
158 |
- end |
|
159 |
- end |
|
160 |
- |
|
161 | 143 |
def log(message, options = {}) |
162 | 144 |
puts "Agent##{id}: #{message}" unless Rails.env.test? |
163 | 145 |
AgentLog.log_for_agent(self, message, options) |
@@ -167,6 +149,32 @@ class Agent < ActiveRecord::Base |
||
167 | 149 |
log(message, options.merge(:level => 4)) |
168 | 150 |
end |
169 | 151 |
|
152 |
+ # Validations and Callbacks |
|
153 |
+ |
|
154 |
+ def sources_are_owned |
|
155 |
+ errors.add(:sources, "must be owned by you") unless sources.all? {|s| s.user == user } |
|
156 |
+ end |
|
157 |
+ |
|
158 |
+ def validate_schedule |
|
159 |
+ unless cannot_be_scheduled? |
|
160 |
+ errors.add(:schedule, "is not a valid schedule") unless SCHEDULES.include?(schedule.to_s) |
|
161 |
+ end |
|
162 |
+ end |
|
163 |
+ |
|
164 |
+ def set_default_schedule |
|
165 |
+ self.schedule = default_schedule unless schedule.present? || cannot_be_scheduled? |
|
166 |
+ end |
|
167 |
+ |
|
168 |
+ def unschedule_if_cannot_schedule |
|
169 |
+ self.schedule = nil if cannot_be_scheduled? |
|
170 |
+ end |
|
171 |
+ |
|
172 |
+ def set_last_checked_event_id |
|
173 |
+ if newest_event_id = Event.order("id desc").limit(1).pluck(:id).first |
|
174 |
+ self.last_checked_event_id = newest_event_id |
|
175 |
+ end |
|
176 |
+ end |
|
177 |
+ |
|
170 | 178 |
# Class Methods |
171 | 179 |
class << self |
172 | 180 |
def cannot_be_scheduled! |
@@ -44,6 +44,16 @@ |
||
44 | 44 |
</div> |
45 | 45 |
</div> |
46 | 46 |
|
47 |
+ |
|
48 |
+ <div class='event-related-region' data-can-create-events="<%= @agent.can_create_events? %>"> |
|
49 |
+ <div class="control-group"> |
|
50 |
+ <%= f.label :keep_events_for, "Keep events", :class => 'control-label' %> |
|
51 |
+ <div class="controls"> |
|
52 |
+ <%= f.select :keep_events_for, options_for_select(Agent::EVENT_RETENTION_SCHEDULES, @agent.keep_events_for), {}, :class => 'span4' %> |
|
53 |
+ </div> |
|
54 |
+ </div> |
|
55 |
+ </div> |
|
56 |
+ |
|
47 | 57 |
<div class="control-group"> |
48 | 58 |
<%= f.label :sources, :class => 'control-label' %> |
49 | 59 |
<div class="controls link-region" data-can-receive-events="<%= @agent.can_receive_events? %>"> |
@@ -84,6 +84,11 @@ |
||
84 | 84 |
|
85 | 85 |
<% if @agent.can_create_events? %> |
86 | 86 |
<p> |
87 |
+ <b>Keep events:</b> |
|
88 |
+ <%= (Agent::EVENT_RETENTION_SCHEDULES.detect {|s| s.last == @agent.keep_events_for } || [@agent.keep_events_for]).first %> |
|
89 |
+ </p> |
|
90 |
+ |
|
91 |
+ <p> |
|
87 | 92 |
<b>Last event created:</b> |
88 | 93 |
<%= @agent.last_event_at ? time_ago_in_words(@agent.last_event_at) + " ago" : "never" %> |
89 | 94 |
</p> |
@@ -6,6 +6,11 @@ |
||
6 | 6 |
</div> |
7 | 7 |
|
8 | 8 |
<p> |
9 |
+ <b>Expires in:</b> |
|
10 |
+ <%= @event.expires_at ? time_ago_in_words(@event.expires_at) : 'never' %> |
|
11 |
+ </p> |
|
12 |
+ |
|
13 |
+ <p> |
|
9 | 14 |
<b>Payload:</b> |
10 | 15 |
<pre><%= Utils.pretty_jsonify @event.payload || {} %></pre> |
11 | 16 |
</p> |
@@ -2,9 +2,10 @@ |
||
2 | 2 |
<div class='row'> |
3 | 3 |
<div class="span5 offset2"> |
4 | 4 |
<h1>Your agents are standing by</h1> |
5 |
- <p>Know the world around you</p> |
|
5 |
+ <p>Huginn monitors the world and acts on your behalf.</p> |
|
6 | 6 |
|
7 |
- <%= link_to "Signup", new_user_registration_path, :class => "btn btn-primary btn-large center" %> |
|
7 |
+ <%= link_to "Login", new_user_session_path, :class => "btn btn-large" %> |
|
8 |
+ <%= link_to "Signup", new_user_registration_path, :class => "btn btn-primary btn-large" %> |
|
8 | 9 |
</div> |
9 | 10 |
<div class="span3"> |
10 | 11 |
<%= image_tag 'odin.jpg', :class => 'img-rounded', :title => "Wägner, Wilhelm. 1882. Nordisch-germanische Götter und Helden. Otto Spamer, Leipzig & Berlin. Page 7." %> |
@@ -0,0 +1,5 @@ |
||
1 |
+class AddKeepEventsForToAgents < ActiveRecord::Migration |
|
2 |
+ def change |
|
3 |
+ add_column :agents, :keep_events_for, :integer, :null => false, :default => 0 |
|
4 |
+ end |
|
5 |
+end |
@@ -11,7 +11,7 @@ |
||
11 | 11 |
# |
12 | 12 |
# It's strongly recommended to check this file into your version control system. |
13 | 13 |
|
14 |
-ActiveRecord::Schema.define(:version => 20131105063248) do |
|
14 |
+ActiveRecord::Schema.define(:version => 20131222211558) do |
|
15 | 15 |
|
16 | 16 |
create_table "agent_logs", :force => true do |t| |
17 | 17 |
t.integer "agent_id", :null => false |
@@ -33,10 +33,11 @@ ActiveRecord::Schema.define(:version => 20131105063248) do |
||
33 | 33 |
t.datetime "last_check_at" |
34 | 34 |
t.datetime "last_receive_at" |
35 | 35 |
t.integer "last_checked_event_id" |
36 |
- t.datetime "created_at", :null => false |
|
37 |
- t.datetime "updated_at", :null => false |
|
36 |
+ t.datetime "created_at", :null => false |
|
37 |
+ t.datetime "updated_at", :null => false |
|
38 | 38 |
t.text "memory", :limit => 2147483647 |
39 | 39 |
t.datetime "last_webhook_at" |
40 |
+ t.integer "keep_events_for", :default => 0, :null => false |
|
40 | 41 |
end |
41 | 42 |
|
42 | 43 |
add_index "agents", ["schedule"], :name => "index_agents_on_schedule" |
@@ -33,6 +33,7 @@ bob_weather_agent: |
||
33 | 33 |
user: bob |
34 | 34 |
schedule: "midnight" |
35 | 35 |
name: "SF Weather" |
36 |
+ keep_events_for: 45 |
|
36 | 37 |
options: <%= { |
37 | 38 |
:location => 94102, |
38 | 39 |
:lat => 37.779329, |
@@ -45,6 +46,7 @@ jane_weather_agent: |
||
45 | 46 |
user: jane |
46 | 47 |
schedule: "midnight" |
47 | 48 |
name: "SF Weather" |
49 |
+ keep_events_for: 30 |
|
48 | 50 |
options: <%= { |
49 | 51 |
:location => 94103, |
50 | 52 |
:lat => 37.779329, |
@@ -303,4 +303,28 @@ describe Agent do |
||
303 | 303 |
end |
304 | 304 |
end |
305 | 305 |
end |
306 |
+ |
|
307 |
+ describe "#create_event" do |
|
308 |
+ describe "when the agent has keep_events_for set" do |
|
309 |
+ before do |
|
310 |
+ agents(:jane_weather_agent).keep_events_for.should > 0 |
|
311 |
+ end |
|
312 |
+ |
|
313 |
+ it "sets expires_at on created events" do |
|
314 |
+ event = agents(:jane_weather_agent).create_event :payload => "hi!" |
|
315 |
+ event.expires_at.should be_within(5).of(agents(:jane_weather_agent).keep_events_for.days.from_now) |
|
316 |
+ end |
|
317 |
+ end |
|
318 |
+ |
|
319 |
+ describe "when the agent does not have keep_events_for set" do |
|
320 |
+ before do |
|
321 |
+ agents(:jane_website_agent).keep_events_for.should == 0 |
|
322 |
+ end |
|
323 |
+ |
|
324 |
+ it "does not set expires_at on created events" do |
|
325 |
+ event = agents(:jane_website_agent).create_event :payload => "hi!" |
|
326 |
+ event.expires_at.should be_nil |
|
327 |
+ end |
|
328 |
+ end |
|
329 |
+ end |
|
306 | 330 |
end |